﻿using System;
using System.Linq.Expressions;
using System.Reflection;
using NetMicro.ExcelMapper.Attributes;

namespace NetMicro.ExcelMapper
{
    /// <summary>
    /// 映射扩展
    /// </summary>
    public static class MapperExtensions
    {
        /// <summary>
        /// 映射列属性
        /// </summary>
        /// <param name="mapper"></param>
        /// <param name="columnName">列名</param>
        /// <param name="propertyInfo">属性信息</param>
        /// <param name="tryTake">导入自定义逻辑</param>
        /// <param name="tryPut">导出自定义逻辑</param>
        public static ExcelMapper Map(this ExcelMapper mapper, string columnName, PropertyInfo propertyInfo,
            Func<IColumnInfo, object, bool> tryTake = null,
            Func<IColumnInfo, object, bool> tryPut = null)
        {
            if (columnName == null) throw new ArgumentNullException(nameof(columnName));
            if (propertyInfo == null) throw new ArgumentNullException(nameof(propertyInfo));

            var columnAttribute = new ColumnAttribute
            {
                Property = propertyInfo,
                Name = columnName,
                TryPut = tryPut,
                TryTake = tryTake,
                Ignored = false
            };

            return mapper.Map(columnAttribute);
        }

        /// <summary>
        /// 映射列属性
        /// </summary>
        /// <param name="mapper"></param>
        /// <param name="columnIndex">列索引</param>
        /// <param name="propertyInfo">属性信息</param>
        /// <param name="tryTake">导入自定义逻辑</param>
        /// <param name="tryPut">导出自定义逻辑</param>
        public static ExcelMapper Map(this ExcelMapper mapper, ushort columnIndex, PropertyInfo propertyInfo,
            Func<IColumnInfo, object, bool> tryTake = null,
            Func<IColumnInfo, object, bool> tryPut = null)
        {
            if (propertyInfo == null) throw new ArgumentNullException(nameof(propertyInfo));

            var columnAttribute = new ColumnAttribute
            {
                Property = propertyInfo,
                Index = columnIndex,
                TryPut = tryPut,
                TryTake = tryTake,
                Ignored = false
            };

            return mapper.Map(columnAttribute);
        }

        /// <summary>
        /// 映射列属性
        /// </summary>
        /// <typeparam name="T">对象</typeparam>
        /// <param name="mapper"></param>
        /// <param name="columnName">列名</param>
        /// <param name="propertyName">属性名</param>
        /// <param name="tryTake">导入自定义逻辑</param>
        /// <param name="tryPut">导出自定义逻辑</param>
        public static ExcelMapper Map<T>(this ExcelMapper mapper, string columnName, string propertyName,
            Func<IColumnInfo, object, bool> tryTake = null,
            Func<IColumnInfo, object, bool> tryPut = null)
        {
            if (mapper == null) throw new ArgumentNullException(nameof(mapper));
            if (columnName == null) throw new ArgumentNullException(nameof(columnName));
            if (propertyName == null) throw new ArgumentNullException(nameof(propertyName));

            Type type = typeof(T);
            PropertyInfo pi = type.GetProperty(propertyName, MapperHelper.BindingFlag);

            if (pi == null && type != typeof(object)) throw new InvalidOperationException($"Cannot find a public property in name of '{propertyName}'.");

            var columnAttribute = new ColumnAttribute
            {
                Property = pi,
                PropertyName = propertyName,
                Name = columnName,
                TryPut = tryPut,
                TryTake = tryTake,
                Ignored = false
            };

            return mapper.Map(columnAttribute);
        }

        /// <summary>
        /// 映射列属性
        /// </summary>
        /// <typeparam name="T">对象</typeparam>
        /// <param name="mapper"></param>
        /// <param name="columnName">列名</param>
        /// <param name="propertySelector">属性选择器</param>
        /// <param name="tryTake">导入自定义逻辑</param>
        /// <param name="tryPut">导出自定义逻辑</param>
        public static ExcelMapper Map<T>(this ExcelMapper mapper, string columnName, Expression<Func<T, object>> propertySelector,
            Func<IColumnInfo, object, bool> tryTake = null,
            Func<IColumnInfo, object, bool> tryPut = null)
        {
            if (mapper == null) throw new ArgumentNullException(nameof(mapper));
            if (columnName == null) throw new ArgumentNullException(nameof(columnName));

            var pi = MapperHelper.GetPropertyInfoByExpression(propertySelector);

            if (pi == null) throw new InvalidOperationException($"Cannot find the property specified by the selector.");

            return mapper.Map(columnName, pi, tryTake, tryPut);
        }

        /// <summary>
        /// 映射列属性
        /// </summary>
        /// <typeparam name="T">对象</typeparam>
        /// <param name="mapper"></param>
        /// <param name="columnIndex">列索引</param>
        /// <param name="propertyName">属性名称</param>
        /// <param name="tryTake">导入自定义逻辑</param>
        /// <param name="tryPut">导出自定义逻辑</param>
        public static ExcelMapper Map<T>(this ExcelMapper mapper, ushort columnIndex, string propertyName,
            Func<IColumnInfo, object, bool> tryTake = null,
            Func<IColumnInfo, object, bool> tryPut = null)
        {
            if (mapper == null) throw new ArgumentNullException(nameof(mapper));
            if (propertyName == null) throw new ArgumentNullException(nameof(propertyName));

            Type type = typeof(T);
            PropertyInfo pi = type.GetProperty(propertyName, MapperHelper.BindingFlag);

            if (pi == null && type != typeof(object)) throw new InvalidOperationException($"Cannot find a public property in name of '{propertyName}'.");

            var columnAttribute = new ColumnAttribute
            {
                Property = pi,
                PropertyName = propertyName,
                Index = columnIndex,
                TryPut = tryPut,
                TryTake = tryTake,
                Ignored = false
            };

            return mapper.Map(columnAttribute);
        }

        /// <summary>
        /// 映射列属性
        /// </summary>
        /// <typeparam name="T">对象</typeparam>
        /// <param name="mapper"></param>
        /// <param name="columnIndex">列索引</param>
        /// <param name="propertySelector">属性选择器</param>
        /// <param name="tryTake">导入自定义逻辑</param>
        /// <param name="tryPut">导出自定义逻辑</param>
        public static ExcelMapper Map<T>(this ExcelMapper mapper, ushort columnIndex, Expression<Func<T, object>> propertySelector,
            Func<IColumnInfo, object, bool> tryTake = null,
            Func<IColumnInfo, object, bool> tryPut = null)
        {
            if (mapper == null) throw new ArgumentNullException(nameof(mapper));
            var pi = MapperHelper.GetPropertyInfoByExpression(propertySelector);
            if (pi == null) throw new InvalidOperationException($"Cannot find the property specified by the selector.");

            return mapper.Map(columnIndex, pi, tryTake, tryPut);
        }

        /// <summary>
        /// 映射列属性
        /// </summary>
        /// <typeparam name="T">对象</typeparam>
        /// <param name="mapper"></param>
        /// <param name="columnIndex">列索引</param>
        /// <param name="propertySelector">属性选择器</param>
        /// <param name="exportedColumnName">要导出的列名.</param>
        /// <param name="tryTake">导入自定义逻辑</param>
        /// <param name="tryPut">导出自定义逻辑</param>
        public static ExcelMapper Map<T>(this ExcelMapper mapper, ushort columnIndex, Expression<Func<T, object>> propertySelector, string exportedColumnName,
            Func<IColumnInfo, object, bool> tryTake = null,
            Func<IColumnInfo, object, bool> tryPut = null)
        {
            if (mapper == null) throw new ArgumentNullException(nameof(mapper));
            var pi = MapperHelper.GetPropertyInfoByExpression(propertySelector);
            if (pi == null) throw new InvalidOperationException($"Cannot find the property specified by the selector.");

            var columnAttribute = new ColumnAttribute
            {
                Property = pi,
                Index = columnIndex,
                Name = exportedColumnName,
                TryPut = tryPut,
                TryTake = tryTake,
                Ignored = false
            };

            return mapper.Map(columnAttribute);
        }

        /// <summary>
        /// 映射列属性
        /// </summary>
        /// <typeparam name="T">对象</typeparam>
        /// <param name="mapper"></param>
        /// <param name="columnIndex">列索引</param>
        /// <param name="propertyName">属性名称</param>
        /// <param name="exportedColumnName">要导出的列名</param>
        /// <param name="tryTake">导入自定义逻辑</param>
        /// <param name="tryPut">导出自定义逻辑</param>
        public static ExcelMapper Map<T>(this ExcelMapper mapper, ushort columnIndex, string propertyName, string exportedColumnName,
            Func<IColumnInfo, object, bool> tryTake = null,
            Func<IColumnInfo, object, bool> tryPut = null)
        {
            if (mapper == null) throw new ArgumentNullException(nameof(mapper));
            if (propertyName == null) throw new ArgumentNullException(nameof(propertyName));

            Type type = typeof(T);
            PropertyInfo pi = type.GetProperty(propertyName, MapperHelper.BindingFlag);

            if (pi == null && type != typeof(object)) throw new InvalidOperationException($"Cannot find a public property in name of '{propertyName}'.");

            var columnAttribute = new ColumnAttribute
            {
                Property = pi,
                PropertyName = propertyName,
                Index = columnIndex,
                Name = exportedColumnName,
                TryPut = tryPut,
                TryTake = tryTake,
                Ignored = false
            };

            return mapper.Map(columnAttribute);
        }

        /// <summary>
        /// 忽略指定属性的所有错误
        /// </summary>
        /// <param name="mapper"></param>
        /// <param name="propertyName">属性名称</param>
        public static ExcelMapper IgnoreErrorsFor<T>(this ExcelMapper mapper, string propertyName)
        {
            if (propertyName == null) throw new ArgumentNullException(nameof(propertyName));

            Type type = typeof(T);
            PropertyInfo pi = type.GetProperty(propertyName, MapperHelper.BindingFlag);

            if (pi == null && type != typeof(object)) throw new InvalidOperationException($"Cannot find a public property in name of '{propertyName}'.");

            var columnAttribute = new ColumnAttribute
            {
                Property = pi,
                PropertyName = propertyName,
                IgnoreErrors = true
            };

            return mapper.Map(columnAttribute);
        }

        /// <summary>
        /// 忽略指定属性的所有错误
        /// </summary>
        /// <param name="mapper"></param>
        /// <param name="propertySelector">属性选择器</param>
        public static ExcelMapper IgnoreErrorsFor<T>(this ExcelMapper mapper, Expression<Func<T, object>> propertySelector)
        {
            var pi = MapperHelper.GetPropertyInfoByExpression(propertySelector);
            if (pi == null) throw new InvalidOperationException($"Cannot find the property specified by the selector.");

            var columnAttribute = new ColumnAttribute
            {
                Property = pi,
                IgnoreErrors = true
            };

            return mapper.Map(columnAttribute);
        }

        /// <summary>
        /// 按名称忽略属性,忽略的属性不会映射为导入和导出
        /// </summary>
        /// <typeparam name="T">对象</typeparam>
        /// <param name="mapper"></param>
        /// <param name="propertyNames">属性名称集合</param>
        public static ExcelMapper Ignore<T>(this ExcelMapper mapper, params string[] propertyNames)
        {
            Type type = typeof(T);

            foreach (string propertyName in propertyNames)
            {
                PropertyInfo pi = type.GetProperty(propertyName, MapperHelper.BindingFlag);

                if (pi == null && type != typeof(object)) // Does not throw for dynamic type.
                {
                    throw new InvalidOperationException($"Cannot find a public property in name of '{propertyName}'.");
                }

                var columnAttribute = new ColumnAttribute
                {
                    Property = pi,
                    PropertyName = propertyName,
                    Ignored = true
                };
                mapper.Map(columnAttribute);
            }

            return mapper;
        }
    }
}
